﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Web;

//***********************************************************************************************    
// Credits: The general idea of the code and some parts of the code are from "Roel van Lisdonk"
// I've converted the solution from an ASPX implementation to a simple deployable solution not
// depending on asp.net session. Many Thanx to Roel.
// Source: http://www.roelvanlisdonk.nl/?p=825
// Twitter: http://twitter.com/#!/roelvanlisdonk
//***********************************************************************************************    

namespace GK.IIS.SignInAsADifferentUser
{
    
    /// <summary>
    /// Sign in as a different user
    /// </summary>
    public class SignInAsHandler : IHttpHandler
    {
        /// <summary>
        /// Page to display the current username
        /// </summary>
        const string PAGE_WHOAMI = "whoami";

        /// <summary>
        /// Page to initiate the switch user function
        /// </summary>
        const string PAGE_SWITCHUSER = "switchuser";
        
        /// <summary>
        /// Page that will handle the authentication part
        /// </summary>
        const string PAGE_AUTH = "auth";

        /// <summary>
        /// Debug page to dump the content of the cache
        /// </summary>
        const string PAGE_CACHE = "cache";

        /// <summary>
        /// Debug page to dump the content of the cache
        /// </summary>
        const string PAGE_CLEANCACHE = "cleancache";


        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            //Don't allow caching to kick in
            context.Response.Expires = 0;
            context.Response.Cache.SetNoStore();
            context.Response.AppendHeader("Pragma", "no-cache");

            
            //Set default page
            var pagename = PAGE_WHOAMI;

            try
            {
                //Parse page name from url
                pagename = System.IO.Path.GetFileNameWithoutExtension(context.Request.PhysicalPath).ToLower();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.TraceError("Could not get page name. Exception: {0}", ex.ToString());
                throw;
            }


            switch (pagename.ToLower())
            {
                case PAGE_CLEANCACHE:
                    //Clear cache
                    IDictionaryEnumerator CacheEnumDelete = context.Cache.GetEnumerator();
                    
                    //Temp store of keys
                    var keysToRemove = new List<string>();
                    while (CacheEnumDelete.MoveNext())
                    {
                        //Is key from out "namespace"?
                        if (CacheEnumDelete.Key.ToString().StartsWith("signinas."))
                        {
                            keysToRemove.Add(CacheEnumDelete.Key.ToString());
                        }
                    }
                    //Remove keys
                    foreach (var key in keysToRemove)
                    {
                        context.Cache.Remove(key);
                    }
                    //Display cache
                    context.Response.Redirect(string.Format("{0}.signinas", PAGE_CACHE));
                    break;
                case PAGE_CACHE:
                    //Display content of cache
                    context.Response.Clear();
                    context.Response.ClearContent();
                    context.Response.ClearHeaders();
                    context.Response.ContentType = "text/plain";
                    IDictionaryEnumerator CacheEnum = context.Cache.GetEnumerator();
                    while (CacheEnum.MoveNext())
                    {
                        //Is key from out "namespace"?
                        if (CacheEnum.Key.ToString().StartsWith("signinas."))
                        {
                            context.Response.Write(string.Format("Key:{0}\tValue:{1}", CacheEnum.Key, CacheEnum.Value));
                            context.Response.Write("\r\n");
                        }
                    }
                    break;
                case PAGE_SWITCHUSER:
                    //Redirect to auth page with unique id
                    context.Response.Redirect(string.Format("{0}.signinas?request={1}", PAGE_AUTH, Guid.NewGuid().ToString()));
                    break;
                case PAGE_AUTH:
                    try
                    {
                        //Current user
                        var user = context.Request.LogonUserIdentity.Name.ToString().ToLower();
                        //Current request id
                        var request = context.Request.QueryString["request"];

                        //Current cache entry if exists
                        var cacheEntry = context.Cache.Get("signinas." + request) as string;
                        if (cacheEntry == null){
                            //Cache not found. Init
                            Trace.TraceInformation("Create request cache object");
                            cacheEntry = string.Format("{0}|{1}", user, 0);
                            context.Cache.Insert("signinas." + request, cacheEntry);
                        }
                        
                        //Simple split of cache entry (first part username)
                        var cacheEntryUser = cacheEntry.Split('|')[0];
                        //Simple split of cache entry (first part attempt to login in)
                        var cacheEntryAttempt = Convert.ToInt32(cacheEntry.Split('|')[1]);

                        //Increment
                        cacheEntryAttempt++;

                        //On first attempt send 401 to initiate authentication request
                        if (cacheEntryAttempt == 1)
                        {
                            Trace.TraceInformation("Attempt == 1");
                            //Update cache entry
                            cacheEntry = string.Format("{0}|{1}", cacheEntryUser, cacheEntryAttempt);
                            context.Cache["signinas." + request] = cacheEntry;
                            //Send 401
                            Send401(context);
                        }
                        else
                        {
                            //In some tests the userd didn't change on first 401 so we need one more if the username is not the same as the previous
                            if (cacheEntryAttempt == 2 && cacheEntryUser.ToLower() == user.ToLower())
                            {
                                Trace.TraceInformation("Attempt == 2 and User equal");
                                cacheEntry = string.Format("{0}|{1}", cacheEntryUser, cacheEntryAttempt);
                                context.Cache["signinas." + request] = cacheEntry;
                                //Send 401
                                Send401(context);
                            }
                            else
                            {
                                Trace.TraceInformation("Done");
                                //TODO: Option 1 - Get referre and redirect to first page that requested the change
                                //TODO: Option 2 - Make a cfg entry in web.config for target page
                                //Redirect to current root page
                                context.Response.Redirect("./");
                            }
                        }
                        

                    }
                    catch(Exception ex)
                    {
                        //BAM
                        Trace.TraceError(ex.ToString());
                    }


                    break;
                case PAGE_WHOAMI:
                default:
                    //Display the current identity and machine name (load balancing)
                    context.Response.Clear();
                    context.Response.ClearContent();
                    context.Response.ClearHeaders();
                    context.Response.ContentType = "text/plain";
                    context.Response.Write(string.Format("Request.LogonUserIdentity: {0}", context.Request.LogonUserIdentity.ToString()));
                    context.Response.Write("\r\n");
                    context.Response.Write(string.Format("Request.LogonUserIdentity.AuthenticationType: {0}", context.Request.LogonUserIdentity.AuthenticationType.ToString()));
                    context.Response.Write("\r\n");
                    context.Response.Write(string.Format("Request.LogonUserIdentity.Name: {0}", context.Request.LogonUserIdentity.Name.ToString()));
                    context.Response.Write("\r\n");
                    context.Response.Write(string.Format("Server.MachineName: {0}", context.Server.MachineName));
                    break;

            }

        }

        private static void Send401(HttpContext context)
        {
            context.Response.Buffer = true;
            context.Response.StatusCode = 401;
            context.Response.StatusDescription = "Unauthorized";

            // A authentication header must be supplied. This header can be changed to Negotiate when using keberos authentication
            context.Response.AddHeader("WWW-Authenticate", context.Request.LogonUserIdentity.AuthenticationType);
            context.Response.End();
        }
    }
}
